#include "fiber.h"
#include "config.h"
#include "macro.h"
#include "log.h"
#include "schedule.h"
#include <atomic>

namespace johnsonli
{
	static Logger::ptr g_logger = LOG_NAME("system");
	
	//c++11 多线程操作中，使用原子变量之后就不需要再使用互斥量来保护该变量了
	static std::atomic<uint64_t> s_fiber_id{ 0 };
	static std::atomic<uint64_t> s_fiber_count{ 0 };
	
	//当前运行协程
	static thread_local Fiber* t_fiber = nullptr;
	//主协程
	static thread_local Fiber::ptr t_threadFiber = nullptr;

	static ConfigVar<uint32_t>::ptr g_fiber_stack_size =
		Config::Lookup<uint32_t>("fiber.stack_size", 1024 * 1024, "fiber stack size");

	//内存分配器
	class MallocStackAllocator
	{
	public:
		static void* Alloc(size_t size)
		{
			return malloc(size);
		}

		static void Dealloc(void* vp)
		{
			return free(vp);
		}
	};

	typedef MallocStackAllocator StackAllocator;

	Fiber::Fiber()
	{
		m_state = EXEC;//主协程创建好后，是运行中状态
		SetThis(this);//设置当前协程 t_fiber

		// 获取此时的上下文
		if (getcontext(&m_ctx))
		{
			DO_ASSERT2(false, "getcontext");
		}

		++s_fiber_count;
		m_id = ++s_fiber_id;
//		LOG_DEBUG(g_logger) << "Fiber::Fiber main id=" + std::to_string(GetId());
	}

	Fiber::Fiber(std::function<void()> cb, size_t stacksize, bool use_caller)
		:m_id(++s_fiber_id)
		,m_cb(cb)
	{
		++s_fiber_count;
		m_stacksize = stacksize ? stacksize : g_fiber_stack_size->getValue();

		m_stack = StackAllocator::Alloc(m_stacksize);

		// 获取当前上下文
		if (getcontext(&m_ctx))
		{
			DO_ASSERT2(false, "getcontext");
		}

		m_ctx.uc_link = nullptr;
		m_ctx.uc_stack.ss_sp = m_stack;
		m_ctx.uc_stack.ss_size = m_stacksize;

		if (use_caller)
		{
			makecontext(&m_ctx, &Fiber::CallerMainFunc, 0);
		}
		else
		{
			makecontext(&m_ctx, &Fiber::MainFunc, 0);
		}
		

//		LOG_DEBUG(g_logger) << "Fiber::Fiber id=" << m_id;
	}


	Fiber::~Fiber()
	{
		--s_fiber_count;
		//子协程才有栈空间
		if (m_stack)//当前是子协程
		{
			DO_ASSERT(m_state == TERM || m_state == INIT || m_state == EXCEPT);
			StackAllocator::Dealloc(m_stack);
		}
		else//当前是主协程
		{
			DO_ASSERT(!m_cb);
			DO_ASSERT(m_state == EXEC);
			Fiber* cur = t_fiber;
			if (cur == this)
			{
				SetThis(nullptr);
				
			}
		}


		LOG_DEBUG(g_logger) << "Fiber::~Fiber id=" << m_id
			<< " total=" << s_fiber_count;
	}

	// 设置重置，复用栈空间
	void Fiber::reset(std::function<void()> cb)
	{
		//子协程重置
		DO_ASSERT(m_stack);
		DO_ASSERT(m_state == TERM || m_state == INIT || m_state == EXCEPT);
		m_cb = cb;

		if (getcontext(&m_ctx))
		{
			DO_ASSERT2(false, "getcontext");
		}

		m_ctx.uc_link = nullptr;
		m_ctx.uc_stack.ss_sp = m_stack;
		m_ctx.uc_stack.ss_size = m_stacksize;

		makecontext(&m_ctx, &Fiber::MainFunc, 0);
		m_state = INIT;
	}


	void Fiber::swapIn()
	{
		//协程切换到后台，并设置为READY状态
		//LOG_DEBUG(g_logger) << "swapIn begin";
		SetThis(this);
		DO_ASSERT(m_state != EXEC);
		m_state = EXEC;

		//Schedule的调度协程 和 当前协程转换
		if (swapcontext(&Scheduler::GetScheduleFiber()->m_ctx, &m_ctx))
		{
			DO_ASSERT2(false, "swapcontext");
		}

		//LOG_DEBUG(g_logger) << "swapIn end";
	}

	void Fiber::swapOut()
	{
		//设置当前协程为主协程
		SetThis(Scheduler::GetScheduleFiber());

		//LOG_ERROR(g_logger) << "t_threadFiber id = " << t_threadFiber->getId() << "this id = " << getId();

		//当前协程 切换到 Schedule的调度协程
		if (swapcontext(&m_ctx, &Scheduler::GetScheduleFiber()->m_ctx))
		{
			DO_ASSERT2(false, "swapcontext");
		}
	}


	void Fiber::call()
	{
		//主协程-->当前协程
		SetThis(this);
		m_state = EXEC;
		if (swapcontext(&t_threadFiber->m_ctx, &m_ctx)) {
			DO_ASSERT2(false, "swapcontext");
		}
	}

	void Fiber::back()
	{
		//当前协程-->主协程
		SetThis(t_threadFiber.get());
		if (swapcontext(&m_ctx, &t_threadFiber->m_ctx)) {
			DO_ASSERT2(false, "swapcontext");
		}
	}

	//设置协程当前协程
	void Fiber::SetThis(Fiber* fiber)
	{
		t_fiber = fiber;
	}

	
	Fiber::ptr Fiber::GetThis()
	{
		//返回当前协程，一个全局静态变量 static thread_globle Thread* t_fiber
		if (t_fiber)
		{
			return t_fiber->shared_from_this();
		}

		Fiber::ptr main_fiber(new Fiber);
		DO_ASSERT(t_fiber == main_fiber.get());
		t_threadFiber = main_fiber;
		return t_fiber->shared_from_this();
	}

	uint64_t Fiber::GetId()
	{
		if (t_fiber)
		{
			return t_fiber->getId();
		}
		return 0;
	}

	//协程切换到后台，并设置为READY状态
	//回到Scheduler协程
	void Fiber::YieldToReadyBySwap()
	{
		Fiber::ptr cur = GetThis();
		cur->m_state = READY;
		cur->swapOut();
	}

	//协程切换到后台，并设置为HOLD状态
	//回到Scheduler协程
	void Fiber::YieldToHoldBySwap()
	{
		Fiber::ptr cur = GetThis();
		cur->m_state = HOLD;
		cur->swapOut();
	}


	//协程切换到后台，并设置为READY状态
	//回到主协程
	void Fiber::YieldToReadyByBack()
	{
		Fiber::ptr cur = GetThis();
		cur->m_state = READY;
		cur->back();
	}

	//协程切换到后台，并设置为HOLD状态
	//回到主协程
	void Fiber::YieldToHoldByBack()
	{
		Fiber::ptr cur = GetThis();
		cur->m_state = HOLD;
		cur->back();
	}

	//总协程数
	uint64_t Fiber::TotalFibers()
	{
		return s_fiber_count;
	}

	//协程的工作函数
	void Fiber::MainFunc()
	{
		Fiber::ptr cur = GetThis();
		DO_ASSERT(cur);

		try
		{
			cur->m_cb();
			cur->m_cb = nullptr;
			cur->m_state = TERM;
		}
		catch (std::exception& e)
		{
			cur->m_state = EXCEPT;
			LOG_ERROR(LOG_ROOT()) << "Fiber Except" << e.what();
		}
		catch (...)
		{
			LOG_ERROR(LOG_ROOT()) << "Fiber Except";
		}

		//需要从当前协程 回到 主协程上下文
		auto p = cur.get();
		cur.reset();
		p->swapOut();

		DO_ASSERT2(false, "never reach" + "fiber id = " + std::to_string(getFiberId()));
	}

	
	void Fiber::CallerMainFunc()
	{
		Fiber::ptr cur = GetThis();
		DO_ASSERT(cur);
		try {
			cur->m_cb();
			cur->m_cb = nullptr;
			cur->m_state = TERM;
		}
		catch (std::exception& ex) {
			cur->m_state = EXCEPT;
			LOG_ERROR(g_logger) << "Fiber Except: " << ex.what()
				<< " fiber_id=" << cur->getId()
				<< std::endl
				<< johnsonli::BacktraceToString();
		}
		catch (...) {
			cur->m_state = EXCEPT;
			LOG_ERROR(g_logger) << "Fiber Except"
				<< " fiber_id=" << cur->getId()
				<< std::endl
				<< johnsonli::BacktraceToString();
		}

		auto raw_ptr = cur.get();
		cur.reset();
		raw_ptr->back();
		DO_ASSERT2(false, "never reach fiber_id=" + std::to_string(raw_ptr->getId()));

	}
}